exports.clyffeServer = (function() {
    var fs = require("fs"),
        http = require("http"),
        sys = require("sys"),
        aop = require("./aop").aop,
        cache = require("./clyffeCache").clyffeCache();
        config = {},
        responseTimeout = 60*1000;
        
    process.on('uncaughtException', function (err) {
        console.log('Caught exception: ' + err);
    });
    
    var defaultHostmap = { 
            "__builtin__" : {
                "onRequest" : function(hostname) {return function (request, response, callback) {
                    console.log("using __builtin__.onRequest for " + hostname);
                    var contentType = "", responseCode = 0;
                    try {
                        var host = hostmap[hostname] || hostmap.default;
                        if (request.method === "GET" || request.method === "HEAD") {
                            var url = require('url').parse(request.url, true);
                            var pathname = url.pathname.replace(/\.\./g, "").replace(/^\//, "");
                            if (!pathname) {
                                pathname = host.defaultPage;
                            }
                            pathname = (host.baseDirectory||(__dirname+"/")) + pathname;
                            console.log(pathname);
                            fs.readFile(pathname, "binary", function (err, data) {
                                if (err) {
                                    responseCode = 404;
                                    contentType = "text/html";
                                    response.writeHead(responseCode, {'Content-Type': contentType});
                                    //callback(responseCode, contentType);
                                    if (request.method==="GET") {
                                        response.end("<h1>404 - File Not Found</h1>");
                                    } else {
                                        response.end();
                                    }
                                    return;
                                }
                                var path = require("path");
                                var contentType = config.contentTypes[path.extname(pathname)];
                                responseCode = 200;
                                response.writeHead(responseCode, {'Content-Type': contentType});
                                
                                if (request.method==="GET") {
                                    response.write(data, "binary");
                                }
                                //callback(responseCode, contentType);
                                response.end();
                            });
                        }
                    } catch (ex) {
                        console.log(ex.toString());
                    }
                };}
            }
        }, 
        hostmap = defaultHostmap;
    
    var itself = { };
    
    var addedHosts = [];
    itself.addHostHandler = function(host) {
        if (hostmap[host.hostname]) {
            console.log( "hostname " + host.hostname + " already in use" );
        }
        console.log("Adding listener for " + host.hostname);
        hostmap[host.hostname] = host;
        addedHosts.push(host.hostname);
    };
    
    itself.removeHostHandler = function(hostname) {
        if (hostname === "__builtin__") {
            return;
        }
        console.log(hostname + " removed");
        hostmap[hostname] = null;
        addedHosts = addedHosts.filter(function(addedHostname) {
            return addedHostname!==hostname;
        });
        delete hostmap[hostname];
    };
    
    itself.clearHostmap = function() {
        for (var hostnameIndex = 0;hostnameIndex<addedHosts.length;hostnameIndex++) {
            delete hostmap[addedHosts[hostnameIndex]];
        }
        addedHosts = [];
    };
    
    var fileWriteLine = function(filePath, line) {
        var sys = require('sys');
        fs.open(filePath, 'a', undefined, function(err, fd) { 
            if(err) throw err; 
            fs.write(fd, line + '\n', undefined, 
                undefined, function(err, written) { 
                    if(err) throw err; 
                    fs.close(fd, function(err) { 
                        if(err) throw err; 
                    }); 
            }); 
        });
    };
    
    itself.log = function(text) {
        try {
            fileWriteLine(config.logDirectory + "log.txt", text);
        } catch (ex) {
            console.log(ex.toString());
        }
    };
    
    var redirectMethod = function(href, redirectStatus) {
            redirectStatus = redirectStatus || 301;
            return function(request, response) {
                    console.log("redirecting to " + href);
                    response.writeHeader(redirectStatus, {"location": href, "content-type" : "text/html"});
                    response.write("The resource you are requesting has moved. <a href=\"" + href + "\">" + href + "</a>");
                    response.end();
                };
        },
        proxyMethod = function(host, port, protocol, href) {
            return function(request, response) {
                    console.log("proxying " + href);
                    var fileClient = require("http").createClient(port, host); 
                    var headers = request.headers;
                    headers.host = host;
                    var fileRequest = fileClient.request("GET", href,
                        headers);
                    fileRequest.end();
                    var respHandler = function(fileResponse) {
                        console.log("proxy file recieved");
                        //data = "";
                        response.writeHeader(fileResponse.statusCode||"", fileResponse.headers);
                        fileResponse.on('data', function (chunk) {
                            //data = data + chunk;
                            response.write(chunk);
                            //console.log(data);
                        });
                        fileResponse.on("end", function () {
                            console.log("proxy file ended");
                            response.end();
                        });
                    }
                    fileRequest.on("response", respHandler);
                };
        };
    
    var ghhDepth = 0,
        getHostHandler = function(hostname, method, href) {
            if (++ghhDepth>10) {
                throw "Recursion depth too deep";
            }
            console.log("locating handler " + hostname + " " + method + " " + href);
            var host = hostmap[hostname] || hostmap.default || {paths:[]};
            console.log("host: " + JSON.stringify(host));
            var path;
            
            for (var pathIndex = 0; pathIndex<host.paths.length; pathIndex++) {
                console.log(pathIndex + " testing '" + href + "' against " + host.paths[pathIndex].exp);
                if (href.match(new RegExp(host.paths[pathIndex].exp))) {
                    path = host.paths[pathIndex];
                    console.log("match found with " + path.exp);
                    
                    if (path.redirect) {
                        return redirectMethod(path.redirect, path.redirectStatus);
                    }
                    
                    if (path.proxy) {
                        var host=path.proxy.host,
                            port=path.proxy.port||80,
                            protocol=path.proxy.protocol||"http";
                        return proxyMethod(host, port, protocol, href);
                    }
                    
                    if (path.rewrite) {
                        var url = require('url').parse(href);
                        
                        var href = path.rewrite;
                        for (var r in url) {if (!Object[r] && typeof url[r] !== "function") {
                            href = href.replace(new RegExp("\\$\{" + r + "\}", "g"), url[r]);
                        }}
                        
                        return getHostHandler(hostname, method, href);
                    }
                    
                    var app = require(host.paths[pathIndex].app);
                    console.log("using " + app.hostname + "." + method + " for " + href);
                    return function (req, res) {
                         return app[method](req, res, host, pathIndex);
                        };
                }
            }
            
            console.log("no match found, using builtin");
            return hostmap.__builtin__[method](hostname);
        };
    
    var server = http.createServer(function (request, response) {
        var url = require('url').parse(request.url, true);
        var host = (/^([^\:]+)/.exec(request.headers["host"]||"")||[])[1]||"default";
        var logString = "";
        var startTime = new Date().valueOf();
        var responseCodeValue = 0,
            headerObject = { },
            beforeWriteHead = function(responseCode, headers) {
                responseCodeValue = responseCode;
                headerObject = headers||{};
                return [responseCode, headers];
            }, 
            beforeEnd = function () {
                clearTimeout(timer);
                logString = 
                    (logString||"") + "\t" + 
                    (responseCodeValue||"") + "\t" + 
                    (headerObject["content-type"]||"") + "\t" + 
                    (new Date().valueOf() - startTime);
            },
            afterEnd = function() {
                fileWriteLine(config.logDirectory + host + ".http.txt", 
                    (logString||"") + "\t" +
                    (new Date().valueOf() - startTime));
            };
        var timer = setTimeout(function() {
            //responseSent(0);
            response.end();
        }, responseTimeout);
        
        aop.joinBefore(response, "writeHead", beforeWriteHead);
        aop.joinBefore(response, "end", beforeEnd);
        aop.joinAfter(response, "end", afterEnd);
        
        try {
            logString = new Date().valueOf() + 
                "\t" + host + 
                "\t" + request.method + 
                "\t" + request.url + 
                "\t" + (request.headers["referer"]||"") + 
                "\t" + (request.headers["user-agent"]||"");
        } catch (ex) { }
        
        try {
            ghhDepth = 0;
            cache.checkCache(request, response,
                getHostHandler(host, 
                    "onRequest", 
                    url.href));
        } catch (ex) {
            itself.log(ex);
        }
    });
    
    var lastmtime = new Date(), initializing = true;
    itself.start = function(c,p) {
        console.log("start called");
        if (c && p && (c.mtime.valueOf()===p.mtime.valueOf() || c.mtime.valueOf()===lastmtime.valueOf())) {
            return;
        }
        if (c && c.mtime) {
            lastmtime = c.mtime;
            console.log(lastmtime.valueOf());
        }
        if (!initializing && !c) {
            return;
        }
        initializing = false;
        fs.readFile(__dirname + "/clyffe.config", "utf8", function (err, data) {
            itself.clearHostmap();
            if (server && server.close) {
                try {
                    server.close();
                    //server = null;
                } catch (ex) { }
            }
            if (err) throw err;
            config = JSON.parse(data);
        
            (function () {
                var port = config.port || 8000,
                    host = config.host||"127.0.0.1",
                    listenCallbackCount = 0;
                server.listen(port, host, function () {
                    if (listenCallbackCount>0) {
                        return;
                    }
                    listenCallbackCount++;
                    console.log("Server running at http://" + host + ":" + port + "/");
                    console.log("configuration file contains " + config.hosts.length + " hosts");
                    for (var hostIndex = 0; hostIndex<config.hosts.length; hostIndex++) {
                        itself.addHostHandler(config.hosts[hostIndex]);
                    }
                });
            })();
        });
    };
    
    itself.start();
    fs.watchFile(__dirname + "/clyffe.config", itself.start);
    
    return itself;
})();